package nachos.vm;

import java.util.ArrayList;
import java.util.Hashtable;

import nachos.machine.Lib;
import nachos.machine.Machine;
import nachos.machine.OpenFile;
import nachos.machine.Processor;
import nachos.machine.TranslationEntry;
import nachos.threads.Lock;

public class SwapManager {
	
	/**
	 * write memory's ppnth page into the swap disk, as pidth process's vpnth page
	 * 
	 */
	public static void write(int pid, int vpn, int ppn) {
		lock.acquire();
		
		// write it into the file
		int desPos;
		boolean exist = swappingContent.containsKey(new KeyItem(pid, vpn));
		if (exist){
			desPos = swappingContent.get(new KeyItem(pid, vpn));
		}
		else{
			Lib.assertTrue(posMan.hasFreePosition());
			desPos = posMan.getFreePosition();
		}
		
		Lib.debug(VMProcess.dbgVM, "swap write : memory[" + ppn*pageSize + "] --> swapFile[" + desPos*pageSize + "]");
		byte[] buf = new byte[pageSize];
		System.arraycopy(Machine.processor().getMemory(), ppn * pageSize, buf, 0, pageSize);
		Lib.assertTrue(-1 != swapFile.write(desPos * pageSize, buf, 0, pageSize));
		
		// register in the swappingContent and the pageInfo
		if (!exist){
			swappingContent.put(new KeyItem(pid, vpn), desPos);			
			TranslationEntry te = new TranslationEntry(); 
			TranslationEntry t = PagingManager.pageTable.get(ppn); 
			te.ppn = ppn;
			te.vpn = vpn;
			te.readOnly = t.readOnly;
			pageInfo.put(desPos, te);
		}
		Lib.debug(VMProcess.dbgVM, "write swap file : pid = " + pid + " vpn = " + vpn + " pos = " + desPos);
		
		lock.release();
		return;
	}
	
	/**
	 * check if this swap disk contains the vpnth page of pidth process
	 */
	public static boolean exists(int pid, int vpn){
		lock.acquire();
		//printSwap();
		boolean res = swappingContent.containsKey(new KeyItem(pid, vpn)); 
		lock.release();
		return res;
	}
	
	static void printSwap(){
		
		Lib.debug(VMProcess.dbgVM, "print swap:");
		for (KeyItem ki : swappingContent.keySet()){
			Lib.debug(VMProcess.dbgVM, "\tpid = " + ki.processID + " vpn = " + ki.vpn + " pos = " + swappingContent.get(ki));
		}
		
		return;
	}
	
	/**
	 * Read the target page's content into the array buf[], 
	 * and return translationEntry of this page.
	 */
	public static TranslationEntry getPage(int pid, int vpn, byte[] buf){
		
		lock.acquire();
		
		// get the real position of this page stored in the swapping file.
		int desPos = swappingContent.get(new KeyItem(pid, vpn));
		
		// read it in
		Lib.assertTrue(swapFile.read(desPos * pageSize, buf, 0, pageSize) != -1);
		
		Lib.debug(VMProcess.dbgVM, "get page from swap file : pid = " + pid + " vpn = " + vpn);
		
		TranslationEntry te = new TranslationEntry(pageInfo.get(desPos)); 
		
		lock.release();
		
		return te;
	}
	
	public static void releaseAll(){
		if (released)
			return;
		swapFile.close();
		VMKernel.fileSystem.remove(filename);
		System.out.println("remove swap file suc");
		released = true;
	}
	
	/**
	 * when a process exit, release its resources in the swap file. 
	 * @param pid process id
	 */
	public static void releaseProcess(int pid){
		ArrayList<KeyItem> toDel = new ArrayList<KeyItem>();
		for (KeyItem ki : swappingContent.keySet()){
			if (ki.processID == pid){
				toDel.add(ki);
			}
		}
		for (KeyItem ki : toDel){
			posMan.setFreePosition(swappingContent.get(ki));
			swappingContent.remove(ki);			
		}
	}

	private final static String filename = "swapFile";
	
	/** the real file contains the swap contents */
	private static OpenFile swapFile = VMKernel.fileSystem.open("swapFile", true);
	
	/** 
	 * the table recording the swapping informations 
	 * key : (pid, vpn), value : page position in the swap file
	 *  
	 * */
	private static Hashtable<KeyItem, Integer> swappingContent = new Hashtable<KeyItem, Integer>();		
	
	/**
	 * store the pages' informations, pos --> TranslationEntry 
	 */
	private static Hashtable<Integer, TranslationEntry> pageInfo = new Hashtable<Integer, TranslationEntry>();
	
	private final static int pageSize = Processor.pageSize;
	
	
	/** with (swapSize) pages' size at most */
	private final static int swapSize = 100;
	
	static byte[] buf = new byte[pageSize];
	
	static public void init(){
		Lib.assertTrue(swapFile != null);		
		
		Lib.bytesFromInt(buf, 0, 0);
		
		swapFile.write(pageSize * (swapSize-1), buf, 0, pageSize);
		
		posMan = new FreePositionManager(swapSize);
	}
	
	private static Lock lock = new Lock(); 
	
	private static boolean released = false;
	
	private static FreePositionManager posMan;
}
